Пример разработки модуля-контроллера периферийного устройства

Для того, чтобы лучше понять, что от вас требуется в рамках лабораторной работы по периферийным устройствам, рассмотрим процесс разработки структурной схемы (не SystemVerilog-описания) для контроллера светодиодов.

В первую очередь, здесь будет продублирована выдержка из спецификации на этот контроллер (общая часть раздела "Описание контроллеров периферийных устройств", а также подраздел "Светодиоды"):

Спецификация контроллера

Общие термины

  1. Под "запросом на запись по адресу 0xАДРЕС" будет пониматься совокупность следующих условий:
    1. Происходит восходящий фронт clk_i.
    2. На входе req_i выставлено значение 1.
    3. На входе write_enable_i выставлено значение 1.
    4. На входе addr_i выставлено значение 0xАДРЕС
  2. Под "запросом на чтение по адресу 0xАДРЕС" будет пониматься совокупность следующих условий:
    1. На входе req_i выставлено значение 1.
    2. На входе write_enable_i выставлено значение 0.
    3. На входе addr_i выставлено значение 0xАДРЕС

Обратите внимание на то, что запрос на чтение должен обрабатываться синхронно (выходные данные должны выдаваться по положительному фронту clk_i).

При описании поддерживаемых режимов доступа по данному адресу используется интуитивно понятное обозначение:

  • R — доступ только на чтение;
  • W — доступ только на запись;
  • RW — доступ на чтение и запись.

В случае отсутствия запроса на чтение, на выходе read_data_o не должно меняться значение (тоже самое было сделано в процессе разработки памяти данных).

Если пришел запрос на запись или чтение, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например запрос на запись по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае запроса на чтение по недоступному адресу, на выходе read_data_o должно остаться прежнее значение.

К примеру, в случае запроса на чтение по адресу 0x0100004 (четвертый байт в адресном пространстве периферийного устройства "переключатели"), на выходе read_data_o должно оказаться значение 32'hdead_beef. В случае отсутствия запроса на чтение (req_i == 0 или write_enable_i == 1), на выходе read_data_o контроллера переключателей должно оказаться значение 32'hfa11_1eaf.

В случае осуществления записи по принятому запросу, необходимо записать данные с сигнала write_data_i в регистр, ассоциированный с адресом addr_i (если разрядность регистра меньше разрядности сигнала write_data_i, старшие биты записываемых данных отбрасываются).

В случае осуществления чтения по принятому запросу, необходимо по положительному фронту clk_i выставить данные с сигнала, ассоциированного с адресом addr_i на выходной сигнал read_data_o (если разрядность сигнала меньше разрядности выходного сигнала read_data_o, возвращаемые данные должны дополниться нулями в старших битах).

Светодиоды

Светодиоды являются простейшим устройством вывода. Поэтому, чтобы задание было интересней, для их управления был добавлен регистр, управляющий режимом вывода данных на светодиоды. Рассмотрим прототип модуля, который вам необходимо реализовать:

module led_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic        clk_i,
  input  logic        rst_i
  input  logic        req_i,
  input  logic        write_enable_i,
  input  logic [31:0] addr_i,
  input  logic [31:0] write_data_i,
  output logic [31:0] read_data_o,

/*
    Часть интерфейса модуля, отвечающая за подключение к периферии
*/
  output logic [15:0]  led_o
);

logic [15:0]  led_val;
logic         led_mode;

endmodule

Данный модуль должен выводить на выходной сигнал led_o данные с регистра led_val. Запись и чтение регистра led_val осуществляется по адресу 0x00. Запись любого значения, превышающего 2¹⁶-1 должна игнорироваться.

Регистр led_mode отвечает за режим вывода данных на светодиоды. Когда этот регистр равен единице, светодиоды должны "моргать" выводимым значением. Под морганием подразумевается вывод значения из регистра led_val на выход led_o на одну секунду (загорится часть светодиодов, соответствующие которым биты шины led_o равны единице), после чего на одну секунду выход led_o необходимо подать нули. Запись и чтение регистра led_mode осуществляется по адресу 0x04. Запись любого значения, отличного от 0 и 1 должна игнорироваться.

Отсчет времени можно реализовать простейшим счетчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счетчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счетчик будет равен 10⁷-1 (счет идет с нуля).

Обратите внимание на то, что адрес 0x24 является адресом сброса. В случае записи по этому адресу единицы вы должны сбросить регистры led_val, led_mode и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр led_rst, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий запроса на запись, адреса сброса и значения записываемых данных равному единице).

Адресное пространство контроллера:

АдресРежим доступаДопустимые значенияФункциональное назначение
0x00RW[0:65535]Чтение и запись в регистр led_val отвечающий за вывод данных на светодиоды
0x04RW[0:1]Чтение и запись в регистр led_mode, отвечающий за режим "моргания" светодиодами
0x24W1Запись сигнала сброса

Реализация схемы контроллера

Для начала, добавим на структурную схему входы и выходы модуля:

../.pic/Basic%20Verilog%20structures/controllers/fig_01.drawio.svg

В первую очередь, спецификация вводит понятия запрос на чтение и запрос на запись. Создадим вспомогательные провода, которые будут сигнализировать о том, что произошел запрос на чтение или запрос на запись:

../.pic/Basic%20Verilog%20structures/controllers/fig_02.drawio.svg

Далее, спецификация накладывает ограничение на допустимые адреса и значения. Поэтому создадим вспомогательные сигналы, сигнализирующие о том, что текущий адрес соответствует одному из регистров контроллера, а данные для записи соответствуют диапазону допустимых значений этих регистров:

../.pic/Basic%20Verilog%20structures/controllers/fig_03.drawio.svg

Теперь, когда подготовительные работы выполнены, начнем с реализации сброса этого контроллера. Сброс может произойти в двух случаях: когда rst_i == 1 либо же в случае запроса на запись единицы по адресу 0x24. Создадим вспомогательный провод rst, который будет равен единице в случае, если произойдет любое из этих событий. Этот сигнал будет сбрасывать все созданные в данном модуле регистры.

../.pic/Basic%20Verilog%20structures/controllers/fig_04.drawio.svg

Продолжим описание контроллера, создав первый из архитектурных регистровled_val. Запись в этот регистр возможна только в случае выполнения трех условий:

  • произошел запрос на запись;
  • addr_i == 0x00;
  • write_data_i находится в диапазоне [0:65535].

Создадим вспомогательный сигнал val_en, который будет равен единице только в случае выполнения этих трех условий:

../.pic/Basic%20Verilog%20structures/controllers/fig_05.drawio.svg

Теперь реализация регистра lev_val становится совершенно тривиальной задачей, ведь у нас есть:

  • сигнал сброса регистра rst;
  • сигнал разрешения записи в регистр val_en;
  • сигнал данных для записи в регистр write_data_i(из которого мы будем брать только младшие 16 бит данных).

../.pic/Basic%20Verilog%20structures/controllers/fig_06.drawio.svg

Аналогичным образом реализуем еще один архитектурный регистр led_mode:

../.pic/Basic%20Verilog%20structures/controllers/fig_07.drawio.svg

Два этих регистра должны управлять поведением выходного сигнала led_o следующим образом:

  1. В случае led_mode == 0 на выходе led_o должно оказаться значение led_val;
  2. В случае led_mode == 1 на выходе led_o должно циклически меняться значение c led_val на 16'd0 и обратно с периодом в одну секунду.

Для реализации счета времени нам потребуется вспомогательный неархитектурный регистр cntr, который станет простейшим счетчиком со сбросом. Мы знаем, что тактовый сигнал нашей схемы будет работать с периодом в 10 МГц. Если каждый такт инкрементировать счетчик на единицу, то за одну секунду счетчик досчитает до 10 миллионов. Первой мыслью может показаться, что нам нужно, чтобы счетчик считал до 10 миллионов, дойдя до которых он бы сбрасывался в ноль, однако в этом случае у нас будут сложности при дальнейшей реализации. Будет куда удобней, если вместо этого счетчик будет считать до 20 миллионов (полного периода смены значения с led_val на 16'd0 и обратно). В этом случае, нам останется всего лишь добавить условие вывода значения на мультиплексор:

  • пока значение счетчика меньше 10 миллионов, на выходе led_o будет значение led_val
  • в противном случае, на выходе led_o будет значение 16'd0.

Таким образом, поведение счетчика описывается следующим образом:

  • счетчик сбрасывается, в следующих случаях:
    • произошел сброс (rst == 1);
    • произошло отключение "моргания" светодиодов (led_mode == 0);
    • счетчик досчитал до 20 миллионов (cntr >= 32'd20_000_000);
  • в остальных ситуациях, счетчик инкрементирует свое значение.

../.pic/Basic%20Verilog%20structures/controllers/fig_08.drawio.svg

Последним этапом описания контроллера будет добавление логики управления выходным сигналом read_data_o.

На управление этим сигналом наложены следующие требования:

  • изменения этого сигнала должны быть синхронными (значит перед выходным сигналом должен стоять регистр);
  • в случае запроса на чтение по поддерживаемому адресу, данный сигнал должен принять значение ассоциированного с этим адресом регистра (дополнив это значение нулями в старших разрядах).
  • в случае отсутствия запроса на чтение, или запроса на чтение по неподдерживаемому адресу, регистр должен сохранить значение

Чтобы регистр сохранял значение между запросами на чтение по поддерживаемому адресу, добавим ему сигнал enable, а на вход данных подадим выход с мультиплексора, выбирающего между доступными источниками данных для чтения.

Таким образом, итоговая схема примет вид:

../.pic/Basic%20Verilog%20structures/controllers/fig_09.drawio.svg